# -*- coding: iso-8859-1 -*-
"""
cart2hexa
hexa2cart
cart_rotation
hexa_rotation
hexround
hexnei
hex2vertex
vertex2hex
vertexround
vertexnei
distanceD6
"""

__author__="Vincent"
__date__ ="$10 mars 2009 23:24:06$"

import types
import math

#Define exceptions
class HexacoordError(Exception): pass
class NotHexValueError(HexacoordError): pass
class NotVertexValueError(HexacoordError): pass

def cart2hexa((x,y), xyshift=(0,0)):
    """Converts cartesian coordinates in hexagonal coordinates
    symmetry = '' or 'x' or 'y' or 'xy'
    """
    xshift, yshift = xyshift
    u = (x + xshift) + (y + yshift) / math.sqrt(3)
    v =  2.0 / math.sqrt(3) * (y + yshift)
    return (u,v)

def hexa2cart((u,v), xyshift=(0,0)):
    """Converts hexagonal coordinates in cartesian coordinates
    symmetry = '' or 'x' or 'y' or 'xy'
    """
    xshift, yshift = xyshift
    x = -xshift + u - 0.5 * v
    y = -yshift + 0.5 * math.sqrt(3) * v
    
    return (x,y)

def cart_rotation((x,y), angle):
    """Computes rotated cartesian coordinates"""
    x_ = x * math.cos(angle) - y * math.sin(angle)
    y_ = x * math.sin(angle) + y * math.cos(angle)
    return (x_, y_)

def hexa_rotation((u,v), angle):
    """Computes rotated hexagonal coordinates"""
    return cart2hexa(cart_rotation(hexa2cart((u, v)), angle))

#TODO: this is an approximation - be careful --> vertexround() is based on it, but does it really work ?

def __hexroughround((u,v)):
    u0 = int(math.floor(u + 0.5))
    v0 = int(math.floor(v + 0.5))
    return (u0, v0)

def hexround((u,v)):
    """Return closest hexagonal center"""
    u0, v0 = __hexroughround((u,v))
    if abs(u - u0 - (v - v0)) <= 0.5:
        return (u0, v0)

def hexnei((u,v)):
    """Return hex neigbor coordinates"""
    # should only take int values
    if not type(u) == types.IntType or not type(v) == types.IntType:
        raise NotHexValueError, "hexnei inputs should be (int, int)"
    
    u0, v0 = hexround((u,v))
    li = [(1,0), (1,1), (0,1), (-1,0), (-1,-1), (0,-1)]
    return tuple([(a+u0, b+v0) for (a,b) in li])

#TODO: TestCase
def hex2vertex((u,v)):
    """return the 2 vertexes this hex has (by convention)"""
    # should only take int values
    if not type(u) == types.IntType or not type(v) == types.IntType:
        raise NotHexValueError, "hex2vertex input should be valid hex coordinates"
    return ((u + 1.0 / 3.0, v - 1.0 / 3.0), (u - 1.0 / 3.0, v + 1.0 / 3.0))

def hex2vertexnei((u,v)):
    """return vertex list for adjacent vertexes to an hexagon"""
    # should only take int values
    if not type(u) == types.IntType or not type(v) == types.IntType:
        raise NotHexValueError, "hex2vertex input should be valid hex coordinates"
    
    li = [(2.0 / 3.0, 1.0 / 3.0),
          (1.0 / 3.0, 2.0 / 3.0),
          (-1.0 / 3.0, 1.0 / 3.0),
          (-2.0 / 3.0, -1.0 / 3.0),
          (-1.0 / 3.0, -2.0 / 3.0),
          (1.0 / 3.0, -1.0 / 3.0)]
    return tuple([(a+u, b+v) for (a,b) in li])

#TODO: TestCase
def vertex2hex((u,v)):
    """return hex to which belongs this vertex (by convention)"""
    # should take value, which are (int /  3.0)
    accuracy = 10
    u_, v_ = 3 * u, 3 * v
    if not round(u_, accuracy) == round(u_) or not round(v_, accuracy) == round(v_):
        raise NotVertexValueError, "vertex2hex input should be valid vertex coordinates"
    return __hexroughround((u,v))

def vertex2hexnei((u,v)):
    """return hex list for adjacent hexagons to a vertex"""
    # should take value, which are (int /  3.0)
    accuracy = 10
    u_, v_ = 3 * u, 3 * v
    if not round(u_, accuracy) == round(u_) or not round(v_, accuracy) == round(v_):
        raise NotVertexValueError, "vertex2hex input should be valid vertex coordinates"

    # find to which hexagon belongs this vertex
    u0, v0 = int(round(u)), int(round(v))
    if u-u0 > 0 and v-v0 < 0 : # we've got 'u' vertex of hex(u0, v0)
        return ((u0,v0), (u0,v0-1), (u0+1,v0))
    elif u-u0 < 0  and v-v0 > 0 : # we've got 'v' vertex of hex(u0, v0)
        return ((u0,v0), (u0,v0+1), (u0-1,v0))
    else : raise NotVertexValueError, "vertex2hex input should be valid vertex coordinates - this one should not be raised" # should not happend here

def vertexround((u,v), dist=2.0/3.0):
    """return closest vertex or None if at equal distance of two or more vertexes"""
    # let's do it slow and dirty
    # first find in which hex is the point
    u0v0 = hexround((u,v))  # hex center
    if not u0v0: # we are very close to vertex, that's good
        a, b = roundhv((u,v))   # so we should get vertex coordinate
        if distanceD6((u,v), (a,b)) < dist:
            return (a,b)
        else:
            return
    else:
        # then compare to each vertex to find the closest - if equality, return None
        vlist = hex2vertexnei(u0v0)    # vertex candidates
        dlist = [distanceD6((u,v), vert) for vert in vlist] # distance between point and vertex candidates

        # min value ? is there duplicate ?
        m = min(dlist)
        if not len([d for d in dlist if d == m]) == 1: # if not only one min value
            return None

        # which index for the smallest distance ?
        i = dlist.index(min(dlist))

        # check if distance is less than 'd'
        if dlist[i] < dist :
            return vlist[i]     # we've found our nearest vertex


def vertexnei((u,v)):
    """Return vertex neigbor coordinates"""
    # should take value, which are (int /  3.0)
    accuracy = 10
    u_, v_ = 3 * u, 3 * v
    if not round(u_, accuracy) == round(u_) or not round(v_, accuracy) == round(v_):
        raise NotVertexValueError, "vertex2hex input should be valid vertex coordinates"

    # return depends on which hexagon belongs this vertex
    u0, v0 = int(round(u)), int(round(v))
    if u-u0 > 0 and v-v0 < 0 : # we've got 'u' vertex of hex(u0, v0)
        return ((u + 1.0 / 3.0, v + 2.0 / 3.0), (u - 2.0 / 3.0, v - 1.0 / 3.0), (u + 1.0 / 3.0, v - 1.0 / 3.0))
    elif u-u0 < 0  and v-v0 > 0 : # we've got 'v' vertex of hex(u0, v0)
        return ((u + 2.0 / 3.0, v + 1.0 / 3.0), (u - 1.0 / 3.0, v + 1.0 / 3.0), (u - 1.0 / 3.0, v - 2.0 / 3.0))
    else : raise NotVertexValueError, "vertex2hex input should be valid vertex coordinates - this one should not be raised" # should not happend here

def roundhv((u,v)):
    """Return either the closest Hex or the closest Vertex"""
    ru = int(math.floor(u + 0.5))
    rv = int(math.floor(v + 0.5))
    # either closer to hexagone center or to one of the two vertexes
    if abs(u - ru - (v - rv)) <= 0.5: # closest to hex center
        return (ru, rv)
    else:   # vertex 'u' or 'v' ?
        vertex_u = (ru + 1.0 / 3.0, rv - 1.0 / 3.0)
        vertex_v = (ru - 1.0 / 3.0, rv + 1.0 / 3.0)
        if distanceD6((u,v), vertex_u) < distanceD6((u,v), vertex_v):
            return vertex_u
        else:
            return vertex_v
        

def distanceD6((u1,v1),(u2,v2)=(0,0)):
    """Return the D6 distance between two points in the hexagonal coordinate system"""
    du = u1 - u2
    dv = v1 - v2
    return (abs(du)+abs(dv)+abs(du-dv)) / 2.0

if __name__ == "__main__":
    print "oucou"
